home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 722 / 722.xpi / chrome / noscript.jar / content / noscript / RequestWatchdog.js < prev    next >
Text File  |  2010-02-12  |  75KB  |  2,213 lines

  1.  
  2. function RequestWatchdog() {
  3.   this.init();
  4. }
  5.  
  6. RequestWatchdog.prototype = {
  7.   
  8.   OBSERVED_TOPICS: ["http-on-modify-request", "http-on-examine-response", "http-on-examine-merged-response"],
  9.   
  10.   init: function() {
  11.     for each (var topic in this.OBSERVED_TOPICS) OS.addObserver(this, topic, true);
  12.   },
  13.   dispose: function() {
  14.     for each (var topic in this.OBSERVED_TOPICS) OS.removeObserver(this, topic, true);
  15.   },
  16.   
  17.   callback: null,
  18.   externalLoad: null,
  19.   noscriptReload: null,
  20.   DOCUMENT_LOAD_FLAGS: CI.nsIChannel.LOAD_DOCUMENT_URI
  21.     | CI.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS, // this for OBJECT subdocs
  22.   
  23.   QueryInterface: xpcom_generateQI([CI.nsIObserver, CI.nsISupportsWeakReference, CI.nsISupports]),
  24.   
  25.   observe: function(channel, topic, data) {
  26.    
  27.     if (!(channel instanceof CI.nsIHttpChannel)) return;
  28.     
  29.     if(ns.consoleDump & LOG_SNIFF) {
  30.       ns.dump(topic + ": " + channel.URI.spec + ", " + channel.loadFlags);
  31.     }
  32.     var loadFlags = channel.loadFlags;
  33.     var isDoc = loadFlags & this.DOCUMENT_LOAD_FLAGS;
  34.      
  35.     switch(topic) {
  36.       case "http-on-modify-request":
  37.         
  38.         HTTPS.forceChannel(channel);
  39.         
  40.         var ncb = channel.notificationCallbacks;
  41.         if (!(loadFlags || ncb || channel.owner)) {
  42.           try {
  43.             if (channel.getRequestHeader("Content-type") == "application/ocsp-request") {
  44.               if (ns.consoleDump) ns.dump("Skipping cross-site checks for OCSP request " + channel.name);
  45.               return;
  46.             }
  47.           } catch(e) {}
  48.         }
  49.         
  50.         if (ncb instanceof CI.nsIXMLHttpRequest && !ns.isCheckedChannel(channel)) {
  51.           if (ns.consoleDump) ns.dump("Skipping cross-site checks for chrome XMLHttpRequest " + channel.name + ", " + loadFlags + ", "
  52.                                       + channel.owner + ", " + !!PolicyState.hints);
  53.           return;
  54.         }
  55.         
  56.         var abeReq = null;
  57.         try {
  58.           
  59.           abeReq = new ABERequest(channel);
  60.           if (this.externalLoad && this.externalLoad === abeReq.destination) {
  61.             abeReq.external = true;
  62.             this.externalLoad = null;
  63.           }
  64.           
  65.           if (isDoc) {
  66.             new DOSChecker(abeReq).run(function() {
  67.               return this.filterXSS(abeReq);
  68.             }, this);  
  69.           }
  70.           
  71.           if (!channel.status) {
  72.             this.handleABE(abeReq, isDoc);
  73.           }
  74.           
  75.         } catch(e) {
  76.           this.die(channel, e);
  77.         }
  78.       break;
  79.       
  80.       
  81.       case "http-on-examine-response":
  82.         STS.processRequest(channel);
  83.         
  84.       case "http-on-examine-merged-response":
  85.         if (isDoc) {
  86.           ns.onContentSniffed(channel);
  87.         } else {
  88.           if (!ns.checkInclusionType(channel))
  89.             return;
  90.         }
  91.         HTTPS.handleSecureCookies(channel);
  92.       break;
  93.     }
  94.   },
  95.   
  96.   die: function(channel, e) {
  97.     this.abort({ channel: channel, reason: e + " --- " + e.stack, silent: true });
  98.   },
  99.   
  100.   handleABE: function(abeReq, isDoc) {
  101.     if (abeReq && ABE.enabled) {
  102.       try {
  103.         // ns.dump("handleABE called for " + abeReq.serial + ", " + abeReq.destination + " at " + Components.stack.caller);
  104.         var res = new DOSChecker(abeReq, true).run(function() {
  105.           return ABE.checkRequest(abeReq);
  106.         });
  107.         if (res) {
  108.           this.notifyABE(res, !(isDoc && res.fatal && ns.getPref("ABE.notify")));  
  109.           if (res.fatal) return true;
  110.         }
  111.       } catch(e) {
  112.         this.die(abeReq.channel, e);
  113.         return true;
  114.       }
  115.     }
  116.     return false;
  117.   },
  118.   
  119.   notifyABE: function(abeRes, silent) {
  120.     var req = abeRes.request;
  121.     var silentLoopback = !ns.getPref("ABE.notify.namedLoopback");
  122.     abeRes.rulesets.forEach(
  123.       function(rs) {
  124.         var lastRule = rs.lastMatch;
  125.         var lastPredicate = lastRule.lastMatch;
  126.         if (lastPredicate.permissive) return;
  127.         
  128.         var action = lastPredicate.action;
  129.         
  130.         ns.log("[ABE] <" + lastRule.destinations + "> " + lastPredicate + " on " + req
  131.           + "\n" + rs.name + " rule:\n" + lastRule);
  132.         
  133.         if (silent || rs != abeRes.lastRuleset) return;
  134.         
  135.         if (lastRule.local && silentLoopback) {
  136.           var host = req.destinationURI.host;
  137.           if (host != "localhost" && host != "127.0.0.1" && req.destinationURI.port <= 0)
  138.             // this should hugely reduce notifications for users of bogus hosts files, 
  139.             // while keeping "interesting" notifications
  140.             var dnsr = DNS.getCached(host);
  141.             if (dnsr && dnsr.entries.indexOf("127.0.0.1") > -1)
  142.               return;
  143.         }
  144.         
  145.         var w = req.window;
  146.         var browser = this.findBrowser(req.channel, w);
  147.         if (browser)
  148.           browser.ownerDocument.defaultView.noscriptOverlay
  149.             .notifyABE({
  150.               request: req,
  151.               action: action,
  152.               ruleset: rs,
  153.               lastRule: lastRule,
  154.               lastPredicate: lastPredicate,
  155.               browser: browser,
  156.               window: w
  157.             });
  158.       }, this);
  159.   },
  160.   
  161.   get dummyPost() {
  162.     const v = CC["@mozilla.org/io/string-input-stream;1"].createInstance();
  163.     v.setData("", 0);
  164.     this.__defineGetter__("dummyPost", function() { return v; });
  165.     return v;
  166.   },
  167.   
  168.   getUnsafeRequest: function(browser) {
  169.     return ns.getExpando(browser, "unsafeRequest");
  170.   },
  171.   setUnsafeRequest: function(browser, request) {
  172.     return ns.setExpando(browser, "unsafeRequest", request);
  173.   },
  174.   
  175.   
  176.   unsafeReload: function(browser, start) {
  177.     ns.setExpando(browser, "unsafeReload", start);
  178.     if (start) {
  179.       const unsafeRequest = this.getUnsafeRequest(browser);
  180.       if (unsafeRequest) {
  181.         // should we figure out what to do with unsafeRequest.loadFlags?
  182.         var wn = browser.webNavigation;
  183.         if(unsafeRequest.window) {
  184.           // a subframe...
  185.           try {
  186.             wn = DOM.getDocShellForWindow(unsafeRequest.window).QueryInterface(CI.nsIWebNavigation);
  187.           } catch(ex) {
  188.             ns.dump(ex);
  189.           }
  190.           unsafeRequest.window = null;
  191.         }
  192.        
  193.         wn.loadURI(unsafeRequest.URI.spec, 
  194.               wn.LOAD_FLAGS_BYPASS_CACHE | 
  195.               wn.LOAD_FLAGS_IS_REFRESH,
  196.               unsafeRequest.referrer, unsafeRequest.postData, null);
  197.         unsafeRequest.issued = true;
  198.       } else {
  199.         browser.reload();
  200.       }
  201.     }
  202.     return start;
  203.   },
  204.  
  205.   isUnsafeReload: function(browser) {
  206.     return ns.getExpando(browser, "unsafeReload");
  207.   },
  208.   
  209.   resetUntrustedReloadInfo: function(browser, channel) {
  210.     if (!browser) return;
  211.     var window = IOUtil.findWindow(channel);
  212.     if (browser.contentWindow == window) {
  213.       if (ns.consoleDump) this.dump(channel, "Top level document, resetting former untrusted browser info");
  214.       this.setUntrustedReloadInfo(browser, false);
  215.     }
  216.   },
  217.   setUntrustedReloadInfo: function(browser, status) {
  218.     return ns.setExpando(browser, "untrustedReload", status);
  219.   },
  220.   getUntrustedReloadInfo: function(browser) {
  221.     return ns.getExpando(browser, "untrustedReload");
  222.   },
  223.   
  224.   _listeners: [],
  225.   addCrossSiteListener: function(l) {
  226.     if (!this._listeners.indexOf(l) > -1) this._listeners.push(l);
  227.   },
  228.   removeCrossSiteListener: function(l) {
  229.     var pos = this._listeners.indexOf(l);
  230.     if (pos > -1) this._listeners.splice(pos);
  231.   },
  232.   
  233.   onCrossSiteRequest: function(channel, origin, browser) {
  234.     for each (var l in this._listeners) {
  235.       l.onCrossSiteRequest(channel, origin, browser, this);
  236.     }
  237.   },
  238.   
  239.   isHome: function(url) {
  240.     return url instanceof CI.nsIURL &&
  241.       this.getHomes().some(function(urlSpec) {
  242.         try {
  243.           return !url.getRelativeSpec(IOS.newURI(urlSpec, null, null));
  244.         } catch(e) {}
  245.         return false;
  246.       });
  247.   },
  248.   getHomes: function(pref) {
  249.     var homes;
  250.     try {
  251.       homes = ns.prefService.getComplexValue(pref || "browser.startup.homepage",
  252.                          CI.nsIPrefLocalizedString).data;
  253.     } catch (e) {
  254.       return pref ? [] : this.getHomes("browser.startup.homepage.override");
  255.     }
  256.     return homes ? homes.split("|") : [];
  257.   },
  258.   
  259.   checkWindowName: function(window) {
  260.     var originalAttempt = window.name;
  261.     
  262.     if (/\s*{[\s\S]+}\s*/.test(originalAttempt)) {
  263.       try {
  264.         ns.json.decode(originalAttempt); // fast track for crazy JSON in name like on NYT
  265.         return;
  266.       } catch(e) {}
  267.     }
  268.     
  269.     if (/[%=\(\\]/.test(originalAttempt) && InjectionChecker.checkJS(originalAttempt)) {
  270.       window.name = originalAttempt.replace(/[%=\(\\]/g, " ");
  271.     }
  272.     if (originalAttempt.length > 11) {
  273.       try {
  274.         if ((originalAttempt.length % 4 == 0)) { 
  275.           var bin = window.atob(window.name);
  276.           if(/[=\(\\]/.test(bin) && InjectionChecker.checkJS(bin)) {
  277.             window.name = "BASE_64_XSS";
  278.           }
  279.         }
  280.       } catch(e) {}
  281.     }
  282.     if (originalAttempt != window.name) {
  283.       ns.log('[NoScript XSS]: sanitized window.name, "' + originalAttempt + '" to "' + window.name + '".');
  284.     }
  285.   },
  286.   
  287.   filterXSS: function(abeReq) {
  288.     
  289.     const channel = abeReq.channel;
  290.     
  291.     const url = abeReq.destinationURI;
  292.     const originalSpec = abeReq.destination;
  293.  
  294.  
  295.     if (this.noscriptReload == originalSpec) {
  296.       // fast cache route for NoScript-triggered reloads
  297.       this.noscriptReload = null;
  298.       try {
  299.         if (ns.consoleDump) {
  300.           this.dump(channel, "Fast reload, original flags: " + 
  301.             channel.loadFlags + ", " + (channel.loadGroup && channel.loadGroup.loadFlags));
  302.         }
  303.         channel.loadFlags = (channel.loadFlags & ~CI.nsIChannel.VALIDATE_ALWAYS) | 
  304.                     CI.nsIChannel.LOAD_FROM_CACHE | CI.nsIChannel.VALIDATE_NEVER;
  305.         if (channel.loadGroup) {
  306.           channel.loadGroup.loadFlags = (channel.loadGroup.loadFlags & ~CI.nsIChannel.VALIDATE_ALWAYS) | 
  307.                   CI.nsIChannel.LOAD_FROM_CACHE | CI.nsIChannel.VALIDATE_NEVER;
  308.         }
  309.         if (ns.consoleDump) {
  310.           this.dump(channel, "Fast reload, new flags: " + 
  311.             channel.loadFlags + ", " + (channel.loadGroup && channel.loadGroup.loadFlags));
  312.         }
  313.       } catch(e) {
  314.         // we may have a problem here due to something Firekeeper 0.2.11 started doing..
  315.         ns.dump(e);
  316.       }
  317.     }
  318.     
  319.     var origin = abeReq.xOrigin;
  320.     var originSite = null;
  321.     var browser = null;
  322.     var window = null;
  323.     var untrustedReload = false;
  324.  
  325.     if (!origin) {
  326.       if ((channel instanceof CI.nsIHttpChannelInternal) && channel.documentURI) {
  327.         if (originalSpec === channel.documentURI.spec) {
  328.            originSite = ns.getSite(abeReq.traceBack);
  329.            if (originSite && abeReq.traceBack !== originalSpec) {
  330.               origin = abeReq.breadCrumbs.join(">>>");
  331.               if (ns.consoleDump) this.dump(channel, "TRACEBACK ORIGIN: " + originSite + " FROM " + origin);
  332.               if ((channel instanceof CI.nsIUploadChannel) && channel.uploadStream) {
  333.                 if (ns.consoleDump) this.dump(channel, "Traceable upload with no origin, probably extension. Resetting origin!");
  334.                 origin = originSite = "";
  335.               }
  336.            } else {
  337.              // check untrusted reload
  338.              browser = this.findBrowser(channel, abeReq.window);
  339.              if (!this.getUntrustedReloadInfo(browser)) {
  340.                if (ns.consoleDump) this.dump(channel, "Trusted reload");
  341.                return;
  342.              }
  343.              origin = originSite = "";
  344.              untrustedReload = true;
  345.              if (ns.consoleDump) this.dump(channel, "Untrusted reload");
  346.            }
  347.         } else {
  348.           origin = channel.documentURI.spec;
  349.           if (ns.consoleDump) this.dump(channel, "ORIGIN (from channel.documentURI): " + origin);
  350.         }
  351.       } else {
  352.         if (ns.consoleDump) this.dump(channel, "***** NO ORIGIN CAN BE INFERRED!!! *****");
  353.       }
  354.     } else {
  355.       if (channel.loadFlags & channel.LOAD_INITIAL_DOCUMENT_URI && channel.originalURI.spec == channel.URI.spec) {
  356.         // clean up after user action
  357.         window = window || abeReq.window;
  358.         browser = browser || this.findBrowser(channel, window);
  359.         this.resetUntrustedReloadInfo(browser, channel);
  360.         var unsafeRequest = this.getUnsafeRequest(browser);
  361.         if (unsafeRequest && unsafeRequest.URI.spec != channel.originalURI.spec && 
  362.             (!window || window == window.top || window == unsafeRequest.window)) {
  363.           this.setUnsafeRequest(browser, null);
  364.         }
  365.       } else origin = origin.replace(/^view-source:/, '');
  366.       if (ns.consoleDump) this.dump(channel, "ORIGIN: " + origin);
  367.     }
  368.     
  369.     const su = SiteUtils;
  370.     originSite = originSite || su.getSite(origin) || '';
  371.     
  372.     var host = channel.URI.host;
  373.     if (host[host.length - 1] == "." && ns.getPref("canonicalFQDN", true) &&
  374.         (Thread.canSpin || ABE.legacySupport)) {
  375.       try {
  376.         if (IOUtil.canDoDNS(channel))
  377.           channel.URI.host = DNS.resolve(host, 2).canonicalName;
  378.         if (ns.consoleDump) ns.dump("Resolving FQDN " + host);
  379.       } catch(ex) {
  380.         this.dump(channel, ex);
  381.       }
  382.     }
  383.     
  384.     var targetSite;
  385.     const globalJS = ns.globalJS;
  386.     var trustedTarget = globalJS;
  387.     if(!trustedTarget) {
  388.       if(ns.autoAllow) {
  389.         window = window || abeReq.window;
  390.         if (window && window == window.top) {
  391.           targetSite = ns.getQuickSite(originalSpec, ns.autoAllow);
  392.           if(targetSite && !ns.isJSEnabled(targetSite)) {
  393.             ns.autoTemp(targetSite);
  394.           }
  395.           targetSite = su.getSite(originalSpec);
  396.         }
  397.       }
  398.       if(!trustedTarget) {
  399.         targetSite = su.getSite(originalSpec);
  400.         trustedTarget = ns.isJSEnabled(targetSite);
  401.         if(!trustedTarget) {
  402.           if (ns.checkShorthands(targetSite)) {
  403.             ns.autoTemp(targetSite);
  404.             trustedTarget = true;
  405.           } else {
  406.             ns.recordBlocked(targetSite);
  407.           }
  408.         }
  409.       }
  410.     }
  411.     
  412.     if (!(origin || (window = abeReq.window))) {
  413.       if (ns.consoleDump) this.dump(channel, "-- This channel doesn't belong to any window/origin: internal browser or extension request, skipping. --");
  414.       return;
  415.     }
  416.       
  417.     if (!targetSite) targetSite = su.getSite(originalSpec);
  418.     
  419.     // noscript.injectionCheck about:config option adds first-line 
  420.     // detection for XSS injections in GET requests originated by 
  421.     // whitelisted sites and landing on top level windows. Value can be:
  422.     // 0 - never check
  423.     // 1 - check cross-site requests from temporary allowed sites
  424.     // 2 - check every cross-site request (default)
  425.     // 3 - check every request
  426.     
  427.     var injectionCheck = ns.injectionCheck;
  428.     
  429.     if (originSite == targetSite) {
  430.       if (injectionCheck < 3) return; // same origin, fast return
  431.     } else {
  432.       this.onCrossSiteRequest(channel, origin, browser = browser || this.findBrowser(channel, abeReq.window));
  433.     }
  434.     
  435.     if (this.callback && this.callback(channel, origin)) return;
  436.     
  437.     if (!trustedTarget) {
  438.       if (InjectionChecker.checkNoscript(InjectionChecker.urlUnescape(originalSpec)) && ns.getPref("injectionCheckHTML", true)) {
  439.         if (ns.consoleDump) this.dump(channel, "JavaScript disabled target positive to HTML injection check!");
  440.       } else {
  441.         if (ns.consoleDump) this.dump(channel, "Target is not Javascript-enabled, skipping XSS checks.");
  442.         return;
  443.       }
  444.     }
  445.     
  446.      // fast return if nothing to do here
  447.     if (!(ns.filterXPost || ns.filterXGet)) return;   
  448.     
  449.     if (!abeReq.external && this.isUnsafeReload(browser = browser || this.findBrowser(channel, abeReq.window))) {
  450.       if (ns.consoleDump) this.dump(channel, "UNSAFE RELOAD of [" + originalSpec +"] from [" + origin + "], SKIP");
  451.       return;
  452.     }
  453.     
  454.     if (ns.filterXExceptions) {
  455.       try {
  456.         if (ns.filterXExceptions.test(unescape(originalSpec)) &&
  457.             !this.isBadException(host)
  458.             ) {
  459.           // "safe" xss target exception
  460.           if (ns.consoleDump) this.dump(channel, "Safe target according to filterXExceptions: " + ns.filterXExceptions.toString());
  461.           return;
  462.         }
  463.   
  464.         if (ns.filterXExceptions.test("@" + unescape(origin))) {
  465.           if (ns.consoleDump) this.dump(channel, "Safe origin according to filterXExceptions: " + ns.filterXExceptions.toString());
  466.           return;
  467.         }
  468.       } catch(e) {}
  469.     }
  470.     
  471.     if (originSite) { // specific exceptions
  472.     
  473.       if (/^https?:\/\/mail\.lycos\.com\/lycos\/mail\/MailCompose\.lycos$/.test(origin) &&
  474.           /\.lycosmail\.lycos\.com$/.test(targetSite) &&
  475.           channel.requestMethod == "POST" &&
  476.           ns.getPref("filterXExceptions.lycosmail")) {
  477.         if (ns.consoleDump) this.dump(channel, "Lycos Mail Exception");
  478.         return;
  479.       }
  480.       
  481.       if (/\.livejournal\.com$/.test(originSite) &&
  482.           /^https?:\/\/www\.livejournal\.com\/talkpost_do\.bml$/.test(originalSpec) &&
  483.           channel.requestMethod == "POST" &&
  484.           ns.getPref("filterXExceptions.livejournal")) {
  485.         if (ns.consoleDump) this.dump(channel, "Livejournal Comments Exception");
  486.         return;
  487.       }
  488.       
  489.       if (originSite == "https://ssl.rapidshare.com" &&
  490.           ns.getBaseDomain(ns.getDomain(targetSite)) == "rapidshare.com" &&
  491.           channel.requestMethod == "POST") {
  492.         if (ns.consoleDump) this.dump(channel, "Rapidshare Upload exception");
  493.         return;
  494.       }
  495.       
  496.       if (originSite == "http://wm.letitbit.net" &&
  497.           /^http:\/\/http\.letitbit\.net:81\/cgi-bin\/multi\/upload\.cgi\?/.test(originalSpec) &&
  498.           channel.requestMethod == "POST") {
  499.         if (ns.consoleDump) this.dump(channel, "letitbit.net Upload exception");
  500.         return;
  501.       }
  502.     
  503.     } else { // maybe data or javascript URL?
  504.       
  505.       if (/^(?:javascript|data):/i.test(origin) && ns.getPref("xss.trustData", true)) {
  506.         originSite = ns.getSite(abeReq.traceBack);
  507.         if (originSite) { 
  508.           origin = abeReq.breadCrumbs.join(">>>");
  509.         }
  510.       }
  511.       
  512.     }
  513.     
  514.     var originalAttempt;
  515.     var injectionAttempt = false;
  516.     var postInjection = false;
  517.     
  518.     window = window || abeReq.window;
  519.     
  520.     // neutralize window.name-based attack
  521.     if (window && window.name) {
  522.       
  523.       if (ns.compatEvernote && window.frameElement && window.name.indexOf("iframe") > 0
  524.           && /^https?:\/\/(?:[a-z]+\.)*evernote\.com\/clip\.action$/.test(originalSpec)
  525.           && channel.requestMethod == "POST") {
  526.         // Evernote Web Clipper hack
  527.         window.frameElement.addEventListener("load", ns.compatEvernote.onload, false);
  528.         if (ns.consoleDump) this.dump(channel, "Evernote frame detected (noscript.compat.evernote)");
  529.         return;
  530.       }
  531.       
  532.       this.checkWindowName(window);
  533.     
  534.     }
  535.    
  536.     if (globalJS || ns.isJSEnabled(originSite) ||
  537.         !origin // we consider null origin as "trusted" (i.e. we check for injections but 
  538.                 // don't strip POST unconditionally) to make some extensions (e.g. Google Gears) 
  539.                 // work. For dangerous edge cases we should have moz-null-principal: now, anyway.
  540.                 || // some goes for Paypal buttons, which we don't require to be on trusted sites
  541.         /^https:\/\/www\.paypal\.com\/(?:ca\/)?cgi-bin\/webscr\b/.test(originalSpec)
  542.       ) {
  543.       
  544.       if (origin && /^http:\/\/(?:[^\/]+.)?facebook\.com\/render_fbml\.php$/.test(originalSpec) &&
  545.             channel.requestMethod == "POST" &&
  546.             ns.getPref("filterXExceptions.fbconnect")) {
  547.         if (ns.consoleDump) this.dump(channel, 'Facebook connect exception');
  548.         return;
  549.       }
  550.       
  551.       this.resetUntrustedReloadInfo(browser = browser || this.findBrowser(channel, window), channel);
  552.       
  553.       // here we exceptionally consider same site also http<->https (target would be blocked by
  554.       // certificate checks if something phishy is going on with DNS)
  555.       
  556.       if (injectionCheck < 3) {
  557.         if (/^https?:/.test(originSite)) {
  558.           var originDomain = ns.getDomain(originSite);
  559.           var targetDomain = ns.getDomain(url);
  560.           if (targetDomain == originDomain) {
  561.             this.dump(channel, "Same domain with HTTP(S) origin");
  562.             return;
  563.           }
  564.         }
  565.       }
  566.       
  567.       // origin is trusted, check for injections
  568.       
  569.       injectionAttempt = injectionCheck && (injectionCheck > 1 || ns.isTemp(originSite)) &&
  570.         (!window || ns.injectionCheckSubframes || window == window.top);
  571.       
  572.       
  573.           
  574.       if (injectionAttempt) {
  575.         postInjection = ns.filterXPost && (!origin || originSite != "chrome:") && channel.requestMethod == "POST" && ns.injectionChecker.checkPost(channel);
  576.         injectionAttempt = ns.filterXGet && ns.injectionChecker.checkURL(
  577.           // Paypal buttons encrypted parameter causes a DOS, strip it out
  578.           /^https:\/\/www\.paypal\.com\/ca\/cgi-bin\/webscr\?cmd=_s-xclick&/.test(originalSpec)
  579.             ? originalSpec.replace(/(?:^|&)encrypted=[^&]+/, '')
  580.             : originalSpec
  581.         );
  582.         
  583.         if (ns.consoleDump) {
  584.           if (injectionAttempt) this.dump(channel, "Detected injection attempt at level " + injectionCheck);
  585.           if (postInjection) this.dump(channel, "Detected POST injection attempt at level "  + injectionCheck);
  586.         }
  587.       }
  588.       
  589.       if (!(injectionAttempt || postInjection)) {
  590.         if (ns.consoleDump) this.dump(channel, "externalLoad flag is " + abeReq.external);
  591.  
  592.         if (abeReq.external) { // external origin ?
  593.           if (ns.consoleDump) this.dump(channel, "External load from " + origin);
  594.           if (this.isHome(url)) {
  595.             if (ns.consoleDump) this.dump(channel, "Browser home page, SKIP");
  596.             return;
  597.           }
  598.           if (ns.getPref("xss.trustExternal", false)) {
  599.             if (ns.consoleDump) this.dump(channel, "noscript.xss.trustExternal is TRUE, SKIP");
  600.             return;
  601.           }
  602.           origin = "///EXTERNAL///";
  603.           originSite = "";
  604.         } else if(ns.getPref("xss.trustTemp", true) || !ns.isTemp(originSite)) { // temporary allowed origin?
  605.           if (ns.consoleDump) {
  606.             this.dump(channel, "Origin " + origin + " is trusted, SKIP");
  607.           }
  608.           return;
  609.         }
  610.         if (ns.consoleDump) 
  611.           this.dump(channel, (abeReq.external ? "External origin" : "Origin " + origin + " is TEMPORARILY allowed") + 
  612.             ", we don't really trust it");
  613.       }
  614.     }
  615.     
  616.     if (untrustedReload && browser) {
  617.       this.resetUntrustedReloadInfo(browser, channel);
  618.     }
  619.  
  620.     // -- DANGER ZONE --
  621.     
  622.     var requestInfo = new RequestInfo(channel, url, origin, window);
  623.  
  624.     // transform upload requests into no-data GETs
  625.     if (ns.filterXPost &&
  626.         (postInjection || !injectionAttempt) && // don't strip trusted to trusted uploads if they passed injection checks 
  627.         (channel instanceof CI.nsIUploadChannel) && channel.uploadStream
  628.       ) {
  629.       channel.requestMethod = "GET";
  630.       requestInfo.unsafeRequest.postData = channel.uploadStream;
  631.       channel.setUploadStream(this.dummyUpload, "", -1);
  632.       this.notify(this.addXssInfo(requestInfo, {
  633.         reason: "filterXPost",
  634.         originalAttempt: originalSpec + (postInjection ? "┬ºDATA┬º" + postInjection : ""),
  635.         silent: untrustedReload
  636.       }));
  637.     }
  638.     
  639.     if (ns.filterXGet && ns.filterXGetRx) {
  640.       var changes = null;
  641.       var xsan = ns.createXSanitizer();
  642.       // sanitize referrer
  643.       if (channel.referrer && channel.referrer.spec) {
  644.         originalAttempt = channel.referrer.spec;
  645.         xsan.brutal = /'"</.test(Entities.convertAll(InjectionChecker.urlUnescape(originalAttempt)));
  646.         try {
  647.           if (channel.referrer instanceof CI.nsIURL) {
  648.             changes = xsan.sanitizeURL(channel.referrer);
  649.           } else {
  650.             channel.referrer.spec =  xsan.sanitizeURIComponent(originalAttempt);
  651.           }
  652.         } catch(e) {
  653.           this.dump("Failed sanitizing referrer " + channel.referrer.spec + ", " + e);
  654.           channel.referrer.spec = "";
  655.         }
  656.         try {
  657.           if (!changes) {
  658.             changes = { 
  659.               minor: !channel.referrer.spec || 
  660.                       unescape(originalAttempt) != unescape(channel.referrer.spec) 
  661.             };
  662.           }
  663.           if (changes.minor) {
  664.             channel.referrer = channel.referrer.clone();
  665.             this.notify(this.addXssInfo(requestInfo, {
  666.               reason: "filterXGetRef",
  667.               originalAttempt: originalSpec + " (REF: " + originalAttempt + ")",
  668.               silent: true,
  669.               sanitizedURI: channel.referrer
  670.             }));
  671.           }
  672.         } catch(e) {
  673.           this.dump("Failed notifying referrer sanitization: " + channel.referrer.spec + ", " + e);
  674.           channel.referrer.spec = "";
  675.           channel.referrer = channel.referrer.clone();
  676.         }
  677.       }
  678.       
  679.       originalAttempt = originalSpec;
  680.       xsan.brutal = injectionAttempt;
  681.       try {
  682.         changes = xsan.sanitizeURL(url);
  683.       } catch(e) {
  684.         changes = xsan.sanitizeURL(url.clone());
  685.         if (changes.major) {
  686.           requestInfo.reason = url.spec;
  687.           this.abort(requestInfo);
  688.           return;
  689.         }
  690.       }
  691.       if (changes.minor) {
  692.         this.proxyHack(channel);
  693.         this.notify(this.addXssInfo(requestInfo, {
  694.           reason: "filterXGet",
  695.           originalAttempt: originalAttempt,
  696.           silent: !changes.major 
  697.         }));
  698.       }
  699.     }
  700.    
  701.     
  702.  
  703.     if (requestInfo.xssMaybe) {
  704.       // avoid surprises from history & cache
  705.       if (channel instanceof CI.nsICachingChannel) {
  706.         
  707.         const CACHE_FLAGS = channel.LOAD_FROM_CACHE | 
  708.                             channel.VALIDATE_NEVER | 
  709.                             channel.LOAD_ONLY_FROM_CACHE;
  710.         // if(channel.loadFlags & CACHE_FLAGS) {
  711.           channel.loadFlags = channel.loadFlags & ~CACHE_FLAGS | channel.LOAD_BYPASS_CACHE;
  712.           if (this.consoleDump) this.dump(channel, "SKIPPING CACHE");
  713.         // }
  714.       }
  715.       
  716.       if (requestInfo.window && 
  717.           (requestInfo.window == requestInfo.window.top || 
  718.           requestInfo.window == requestInfo.unsafeRequest.window)
  719.         ) {
  720.         this.setUnsafeRequest(requestInfo.browser, requestInfo.unsafeRequest);
  721.       }
  722.     }
  723.   },
  724.   
  725.   isBadException: function(host) {
  726.     // TLD check for google search
  727.     var m = host.match(/\bgoogle\.((?:[a-z]{1,3}\.)?[a-z]+)$/i);
  728.     return m && ns.getPublicSuffix(host) != m[1];
  729.   },
  730.   
  731.   
  732.   
  733.   proxyHack: function(channel) {
  734.     // Work-around for channel.URI not being used directly here:
  735.     // http://mxr.mozilla.org/mozilla/source/netwerk/protocol/http/src/nsHttpChannel.cpp#504
  736.     
  737.     var proxyInfo = IOUtil.getProxyInfo(channel);
  738.      if (proxyInfo && proxyInfo.type == "http") {
  739.        if (channel.URI.userPass == "") {
  740.          channel.URI.userPass = "xss:xss";
  741.          // resetting this bit will avoid auth confirmation prompt
  742.          channel.loadFlags = channel.loadFlags & ~channel.LOAD_INITIAL_DOCUMENT_URI;
  743.        }
  744.      }
  745.   },
  746.   
  747.   abort: function(requestInfo) {
  748.     var channel = requestInfo.channel;
  749.     
  750.     if (channel instanceof CI.nsIRequest)
  751.       IOUtil.abort(channel);
  752.     
  753.     this.dump(channel, "Aborted - " + requestInfo.reason);
  754.  
  755.     this.notify(requestInfo);
  756.   },
  757.   
  758.   mergeDefaults: function(o1, o2) {
  759.     for (p in o2) {
  760.       if (!(p in o1)) o1[p] = o2[p];
  761.     }
  762.     return o1;
  763.   },
  764.   
  765.   addXssInfo: function(requestInfo, xssInfo) {
  766.     try {
  767.       requestInfo.window = requestInfo.window || IOUtil.findWindow(requestInfo.channel);
  768.       requestInfo.browser = requestInfo.browser || (requestInfo.window && 
  769.                             DOM.findBrowserForNode(requestInfo.window));
  770.     } catch(e) {}
  771.     requestInfo.xssMaybe = true;
  772.     return this.mergeDefaults(xssInfo, requestInfo);
  773.   },
  774.   
  775.   notify: function(requestInfo) {
  776.     var msg = "[NoScript XSS] " + ns.getString("xss.reason." + requestInfo.reason, [ 
  777.         requestInfo.originalAttempt || "N/A",
  778.         requestInfo.unsafeRequest && requestInfo.unsafeRequest.origin || "",
  779.         requestInfo.sanitizedURI && requestInfo.sanitizedURI.spec || ""
  780.       ]);
  781.     this.dump(requestInfo.channel, "Notifying " + msg + "\n\n\n");
  782.     ns.log(msg);
  783.    
  784.     try {
  785.       if (requestInfo.silent || !requestInfo.window || !ns.getPref("xss.notify", true)) 
  786.         return;
  787.       if(requestInfo.window != requestInfo.window.top) { 
  788.         // subframe
  789.  
  790.         var cur = this.getUnsafeRequest(requestInfo.browser);
  791.         if(cur && !cur.issued) return;
  792.         
  793.         requestInfo.unsafeRequest.window = requestInfo.window;
  794.         this.observeSubframeXSS(requestInfo.originalAttempt, requestInfo.unsafeRequest);
  795.         
  796.         if(!ns.getPref("xss.notify.subframes", true))
  797.           return;
  798.  
  799.         var overlay = ns.findOverlay(requestInfo.browser);
  800.         if(overlay) overlay.notifyXSS(requestInfo);
  801.       }
  802.       IOUtil.attachToChannel(requestInfo.channel, "noscript.XSS", requestInfo);
  803.     } catch(e) {
  804.       dump(e + "\n");
  805.     }
  806.   },
  807.   
  808.   observeSubframeXSS: function(url, unsafeRequest) {
  809.     unsafeRequest.window.addEventListener("unload", function(ev) {
  810.         var w = ev.currentTarget;
  811.         if(w.location.href != url) return; 
  812.         w.removeEventListener("unload", arguments.callee, false);
  813.         unsafeRequest.window = null;
  814.      }, false);
  815.   },
  816.   
  817.   
  818.   findBrowser: function(channel, window) {
  819.     return DOM.findBrowserForNode(window || IOUtil.findWindow(channel));
  820.   },
  821.   
  822.   dump: function(channel, msg) {
  823.     if (!(ns.consoleDump & LOG_XSS_FILTER)) return;
  824.     dump("[NoScript] ");
  825.     dump((channel.URI && channel.URI.spec) || "null URI?" );
  826.     if (channel.originalURI && channel.originalURI.spec != channel.URI.spec) {
  827.       dump(" (" + channel.originalURI.spec + ")");
  828.     }
  829.     dump(" *** ");
  830.     dump(msg);
  831.     dump("\n");
  832.   }
  833.   
  834.   
  835. }
  836.  
  837.  
  838. var Entities = {
  839.   
  840.   get htmlNode() {
  841.     delete this.htmlNode;
  842.     return this.htmlNode =
  843.       (function() {
  844.         try {
  845.           // we need a loose HTML node, only way to get it today seems using hidden window
  846.           var as = CC["@mozilla.org/appshell/appShellService;1"].getService(CI.nsIAppShellService);
  847.           as.hiddenDOMWindow.addEventListener("unload", function(ev) {
  848.             ev.currentTarget.removeEventListener("unload", arguments.callee, false);
  849.             Entities.htmlNode = null;
  850.             doc = null;
  851.             // dump("*** Free Entities.htmlNode ***\n");
  852.           }, false);
  853.           return as.hiddenDOMWindow.document.createElementNS(HTML_NS, "body");
  854.         } catch(e) {
  855.           dump("[NoSript Entities]: Cannot grab an HTML node, falling back to XHTML... " + e + "\n");
  856.           return CC["@mozilla.org/xul/xul-document;1"]
  857.             .createInstance(CI.nsIDOMDocument)
  858.             .createElementNS(HTML_NS, "body")
  859.         }
  860.       })()
  861.   },
  862.   convert: function(e) {
  863.     try {
  864.       this.htmlNode.innerHTML = e;
  865.       var child = this.htmlNode.firstChild || null;
  866.       return child && child.nodeValue || e;
  867.     } catch(ex) {
  868.       return e;
  869.     }
  870.   },
  871.   convertAll: function(s) {
  872.     return s.replace(/[\\&][^<>]+/g, function(e) { return Entities.convert(e) });
  873.   },
  874.   convertDeep: function(s) {
  875.     for (var prev = null; (s = this.convertAll(s)) != prev; prev = s);
  876.     return s;
  877.   },
  878.   neutralize: function(e, whitelist) {
  879.     var c = this.convert(e);
  880.     return (c == e) ? c : (whitelist && whitelist.test(c) ? e : e.replace(";", ","));
  881.   },
  882.   neutralizeAll: function(s, whitelist) {
  883.     return s.replace(/&[\w#-]*?;/g, function(e) { return Entities.neutralize(e, whitelist || null); });
  884.   }
  885. };
  886.  
  887. function SyntaxChecker() {
  888.   this.sandbox = new CU.Sandbox("about:");
  889. }
  890.  
  891. SyntaxChecker.prototype = {
  892.   lastError: null,
  893.   lastFunction: null,
  894.   check: function(script) {
  895.     this.sandbox.script = script;
  896.      try {
  897.        return !!(this.lastFunction = this.ev("new Function(script)"));
  898.      } catch(e) {
  899.        this.lastError = e;
  900.      }
  901.      return false;
  902.   },
  903.   unquote: function(s, q) {
  904.     if (!(s[0] == q && s[s.length - 1] == q &&
  905.         !s.replace(/\\./g, '').replace(/^(['"])[^\n\r]*?\1/, "")
  906.       )) return null;
  907.     try {
  908.       return this.ev(s);
  909.     } catch(e) {}
  910.     return null;
  911.   },
  912.   ev: function(s) {
  913.     return CU.evalInSandbox(s, this.sandbox);
  914.   }
  915. };
  916.  
  917. function fuzzify(s) {
  918.   return s.replace(/\w/g, '\\W*$&');
  919. }
  920.  
  921. const IC_COMMENT_PATTERN = '\\s*(?:\\/[\\/\\*][\\s\\S]+)?';
  922. const IC_WINDOW_OPENER_PATTERN = fuzzify("alert|confirm|prompt|open|print|show") + "\\w*" + fuzzify("Dialog");
  923. const IC_EVAL_PATTERN = fuzzify('eval|set(?:Timeout|Interval)|[fF]unction|Script|toString|Worker|') + IC_WINDOW_OPENER_PATTERN;
  924. const IC_EVENT_PATTERN = "on(?:e(?:rror(?:update)?|nd)|c(?:o(?:nt(?:extmenu|rolselect)|py)|ut|lick|(?:ellc)?hange)|m(?:o(?:ve(?:end|start)?|use(?:o(?:ut|ver)|up|(?:mo|lea)ve|down|wheel|enter))|essage)|lo(?:ad|secapture)|d(?:r(?:ag(?:en(?:d|ter)|drop|over|leave|start)?|op)|ata(?:setc(?:hanged|omplete)|available)|blclick|eactivate)|s(?:t(?:op|art)|elect(?:start)?|croll|ubmit)|b(?:e(?:for(?:e(?:c(?:ut|opy)|p(?:aste|rint)|u(?:pdate|nload)|activate|editfocus)|deactivate)|gin)|lur|ounce)|p(?:ast|ropertychang)e|key(?:up|down|press)|f(?:o(?:cus(?:in|out)?|rm(?:input|change))|i(?:nish|lterchange))|in(?:put|valid)|a(?:fter(?:print|update)|bort|ctivate)|r(?:e(?:s(?:et|ize)|peat|adystatechange)|ow(?:e(?:xit|nter)|s(?:delete|inserted)))|zoom|help|unload)"
  925.   // generated by html5_events.pl, see http://mxr.mozilla.org/mozilla-central/source/parser/html/nsHtml5AtomList.h
  926.   ;
  927. const IC_EVENT_DOS_PATTERN =
  928.       "\\b(?:" + IC_EVENT_PATTERN + ")[\\s\\S]*=[\\s\\S]*\\b(?:" + IC_WINDOW_OPENER_PATTERN + ")\\b"
  929.       + "|\\b(?:" + IC_WINDOW_OPENER_PATTERN + ")\\b[\\s\\S]+\\b(?:" + IC_EVENT_PATTERN + ")[\\s\\S]*=";
  930.       
  931. var InjectionChecker = {
  932.   fuzzify: fuzzify,
  933.   syntax: new SyntaxChecker(),
  934.   _log: function(msg, t, i) {
  935.     if (msg) msg = this._printable(msg);
  936.     if(!(i || t)) {
  937.       msg += " - LINES: ";
  938.       var lines = [];
  939.       for (var stack = Components.stack; (stack = stack.caller);) {
  940.         lines.push(stack.lineNumber);
  941.       }
  942.       msg += lines.join(", ");
  943.     }
  944.     else {
  945.       if (t) msg += " - TIME: " + (Date.now() - t);
  946.       if (i) msg += " - ITER: " + i;
  947.     }
  948.     this.dump("[NoScript InjectionChecker] " + msg + "\n");
  949.   },
  950.   
  951.   _printable: function (msg) {
  952.     return msg.toString().replace(/[^\u0020-\u007e]/g, function(s) { return "{" + s.charCodeAt(0).toString(16) + "}"; });
  953.   },
  954.   
  955.   dump: dump,
  956.   log: function() {},
  957.   get logEnabled() { return this.log == this._log; },
  958.   set logEnabled(v) { this.log = v ? this._log : function() {}; },
  959.   
  960.   
  961.   bb: function(brac, s, kets) {
  962.     for(var j = 3; j-- > 0;) {
  963.       s = brac + s + kets;
  964.       if (this.checkJSSyntax(s)) return true;
  965.     }
  966.     return false;
  967.   },
  968.   
  969.   checkJSSyntax: function(s) {
  970.     // bracket balancing for micro injections like "''), e v a l (name,''"
  971.     if (/^(?:''|"")?[^\('"]*\)/.test(s)) return this.bb("x(\n", s, "\n)");
  972.     if (/^(?:''|"")?[^\['"]*\\]/.test(s)) return this.bb("y[\n", s, "\n]");
  973.     if (/^(?:''|"")?[^\{'"]*\}/.test(s)) return this.bb("function z() {\n", s, "\n}");
  974.     
  975.     s += " /* COMMENT_TERMINATOR */\nDUMMY_EXPR";
  976.     if (this.syntax.check(s)) {
  977.       this.log("Valid fragment " + s);
  978.       return true;
  979.     }
  980.     return false;
  981.   },
  982.   
  983.   get breakStops() {
  984.     var def = "\\/\\?&#;\\s<>"; // we stop on URL, JS and HTML delimiters
  985.     var bs = {
  986.       nq: new RegExp("[" + def + "]")
  987.     };
  988.     Array.forEach("'\"", // special treatment for quotes
  989.       function(c) { bs[c] = new RegExp("[" + def + c + "]"); }
  990.     );
  991.     delete this.breakStops;  
  992.     return this.breakStops = bs;
  993.   },
  994.   
  995.   collapseChars: function(s) {
  996.     return s.replace(/\;+/g, ';').replace(/\/{2,}/g, '//')
  997.       .replace(/\s+/g, function(s) {
  998.       return /\n/g.test(s) ? '\n' : ' ';  
  999.     });
  1000.   },
  1001.   
  1002.   reduceBackSlashes: function(bs) {
  1003.     return bs.length % 2 ? "" : "\\";
  1004.   },
  1005.   
  1006.   reduceQuotes: function(s) {
  1007.     if (s[0] == '/') {
  1008.       // reduce common leading path fragment resembling a regular expression or a comment
  1009.       s = s.replace(/^\/[^\/\n\r]+\//, '_RX_').replace(/^\/\/[^\r\n]*/, '//_COMMENT_');
  1010.     }
  1011.     
  1012.     if (/\/\*/.test(s)) // C-style comments, would make everything really tricky
  1013.       return s;
  1014.     
  1015.     
  1016.     if (/['"\/]/.test(s)) {
  1017.     
  1018.       // drop noisy backslashes
  1019.       s = s.replace(/\\{2,}/g, this.reduceBackSlashes);
  1020.       
  1021.       // drop escaped quotes
  1022.       s = s.replace(/\\["'\/]/g, "EQ");
  1023.       var expr;
  1024.       for(;;) {
  1025.          expr = s.replace(/(^[^'"\/]*[;,\+\-=\(\[]\s*)\/[^\/]+\//g, "$1_RX_")
  1026.                 .replace(/(^[^'"\/]*)(["']).*?\2/g, "$1_QS_");
  1027.          if(expr == s) break;
  1028.          s = expr;
  1029.       }
  1030.     }
  1031.     
  1032.                       // remove c++ style comments    
  1033.     return s.replace(/^([^'"\\]*?)\/\/[^\r\n]*/g, "//_COMMENT_");
  1034.   },
  1035.   
  1036.   reduceURLs: function(s) {
  1037.     // nested URLs with protocol are parsed as C++ style comments, and since
  1038.     // they're potentially very expensive, we preemptively remove them if possible
  1039.     while (/^[^'"]*?:\/\//.test(s)) {
  1040.       s = s.replace(/:\/\/.*/, ':');
  1041.     }    
  1042.     s = s.replace(/:\/\/[^'"\n]*/g, ':');
  1043.     return (/\bhttps?:$/.test(s) && !/\bh\W*t\W*t\W*p\W*s?.*=/.test(s))
  1044.       ? s.replace(/https?:$/)
  1045.       : s;
  1046.   },
  1047.   
  1048.   reduceJSON: function(s) {
  1049.     var m, whole, qred, prev;
  1050.     const toStringRx = /^function\s*toString\(\)\s*{\s*\[native code\]\s*\}$/;
  1051.     // optimistic case first, one big JSON block
  1052.     do {
  1053.       whole = s;
  1054.       m = s.match(/{[\s\S]*}/);
  1055.       if (!m) return s;
  1056.       
  1057.       expr = m[0];
  1058.       var json = ns.json;
  1059.       if (json) {
  1060.         try {
  1061.           if (!toStringRx.test(json.decode(expr).toString))
  1062.             return s;
  1063.           
  1064.           this.log("Reducing big JSON " + expr);
  1065.           return s.replace(expr, '_JSON_');
  1066.         } catch(e) {}
  1067.       }
  1068.       
  1069.       // heavier duty, scattered JSON blocks
  1070.       while((m = s.match(/\{[^\{\}:]+:[^\{\}]+\}/g))) {
  1071.         prev = s;
  1072.   
  1073.         for each(expr in m) {
  1074.           if (json) try {
  1075.             if (!toStringRx.test(json.decode(expr).toString))
  1076.               continue;
  1077.             
  1078.             this.log("Reducing JSON " + expr);
  1079.             s = s.replace(expr, '"_JSON_"');
  1080.             continue;
  1081.           } catch(e) {}
  1082.           
  1083.           if (/\btoString\b[\s\S]*:/.test(expr)) continue;
  1084.           
  1085.           qred = this.reduceQuotes(expr);
  1086.           if (/\{(?:\s*(?:(?:\w+:)+\w+)+;\s*)+\}/.test(qred)) {
  1087.              this.log("Reducing pseudo-JSON " + expr);
  1088.              s = s.replace(expr, '"_PseudoJSON_"');
  1089.           } else if (!/[\(=\.]|[^:\s]\s*\[|:\s*(?:location|document|eval|open|show\w*Dialog)\b/.test(qred) && 
  1090.              this.checkJSSyntax("JSON = " + qred) // no-assignment JSON fails with "invalid label"
  1091.           ) { 
  1092.             this.log("Reducing slow JSON " + expr);
  1093.             s = s.replace(expr, '"_SlowJSON_"');
  1094.           }
  1095.         }
  1096.         
  1097.         if (s == prev) break;
  1098.       }
  1099.       
  1100.     } while (s != whole);
  1101.  
  1102.     return s;
  1103.   },
  1104.   
  1105.   reduceXML: function(s) {
  1106.     var t;
  1107.     while(/^[^"]*</.test(s)) {
  1108.         t = s.replace(/^([^"]*)<\??\s*\/?[a-zA-Z][\w\:\-]+(?:[\s\+]+[\w\:\-]+="[\w\:\-\/\.#%\s\+]*")*[\+\s]*\/?\??>/, '$1;xml;');
  1109.         if (t == s) break;
  1110.         s = t;
  1111.     }
  1112.     if (t) { s = s.replace(/(?:\s*;xml;\s*)+/g, ';xml;') };
  1113.     return s;
  1114.   },
  1115.  
  1116.   _singleAssignmentRx: new RegExp(
  1117.     "(?:\\b" + fuzzify('document') + "\\b[\\s\\S]*\\.|\\s" + fuzzify('setter') + "\\b[\\s\\S]*=)|/.*/[\\s\\S]*(?:\\.(?:"
  1118.       + fuzzify('source|toString') + ")|\\[)|" + IC_EVENT_DOS_PATTERN
  1119.   ),
  1120.   _riskyAssignmentRx: new RegExp(
  1121.     "\\b(?:" + fuzzify('location|innerHTML') + ")\\b[\\s\\S]*="
  1122.   ),
  1123.   _nameRx: new RegExp(
  1124.     "=[\\s\\S]*\\b" + fuzzify('name') + "\\b"
  1125.   ),
  1126.   
  1127.   _maybeJSRx: new RegExp(
  1128.     // identifier's tail...         optional comment...         
  1129.     '[\\w$\\u0080-\\uFFFF\\]\\)]' + IC_COMMENT_PATTERN + 
  1130.     // accessor followed by function call or assignment.    
  1131.      '(?:(?:[\\[]|\\.\\D)[\\s\\S]*(?:\\([\\s\\S]*\\)|=)' +
  1132.        // double function call
  1133.        '|\\([\\s\\S]*\\([\\s\\S]*\\)' +
  1134.      ')|\\b(?:' + IC_EVAL_PATTERN +
  1135.       ')\\b[\\s\\S]*\\(|\\b(?:' +
  1136.       fuzzify('setter|location|innerHTML') +
  1137.       ')\\b[\\s\\S]*=|' +
  1138.       IC_EVENT_DOS_PATTERN +
  1139.       "|=[s\\\\[ux]?\d{2}" // escape (unicode/ascii/octal)
  1140.   ),
  1141.   
  1142.   _jsSpecialFuncsRx: new RegExp(
  1143.     "\\b(?:" + IC_EVAL_PATTERN + "|on\\w+)\\s*\\("
  1144.   ),
  1145.   
  1146.   maybeJS: function(expr) {
  1147.     if(/^(?:[^\(\)="']+=[^\(='"\[]+|(?:[\?a-z_0-9;,&=\/]|\.[\d\.])*)$/i.test(expr) && !/\b=[\s\S]*_QS_\b/.test(expr)) // commonest case, single assignment or simple chained assignments, no break
  1148.       return this._singleAssignmentRx.test(expr) || this._riskyAssignmentRx.test(expr) && this._nameRx.test(expr);
  1149.     if (/^(?:[\w\-\.]+\/)*\(*[\w\-\s]+\([\w\-\s]+\)[\w\-\s]*\)*$/.test(expr)) // typical "call like" Wiki URL pattern + bracketed session IDs
  1150.       return this._jsSpecialFuncsRx.test(expr);
  1151.     
  1152.     return this._maybeJSRx.test(
  1153.         expr.replace(/(?:^|[\/;&#])[\w\-]+\.[\w\-]+[\?;\&#]/g, '', expr) // remove neutral dotted substrings
  1154.     ); 
  1155.   },
  1156.   
  1157.   checkNonTrivialJSSyntax: function(expr) {
  1158.     return this.maybeJS(this.reduceQuotes(expr)) && this.checkJSSyntax(expr);
  1159.   },
  1160.   
  1161.   checkLastFunction: function() {
  1162.     var fn = this.syntax.lastFunction;
  1163.     if (!fn) return false;
  1164.     var m = fn.toSource().match(/\{([\s\S]*)\}/);
  1165.     if (!m) return false;
  1166.     var expr = m[1];
  1167.     return /=[\s\S]*cookie|\b(?:setter|document|location|innerHTML|\.\W*src)[\s\S]*=|[\w$\u0080-\uffff\)\]]\s*[\[\(]/.test(expr) ||
  1168.             this.maybeJS(expr);
  1169.   },
  1170.   
  1171.   _createInvalidRanges: function() {
  1172.     function x(n) { return '\\x' + n.toString(16); }
  1173.     
  1174.     var ret = "";
  1175.     var first = -1;
  1176.     var last = -1;
  1177.     var cur = 0x7e;
  1178.     while(cur++ <= 0xff) {
  1179.       try {
  1180.         ev\u0061l("var _" + String.fromCharCode(cur) + "_=1");
  1181.       } catch(e) {
  1182.         if (!/illegal char/.test(e.message)) continue;
  1183.         if (first == -1) {
  1184.           first = last = cur;
  1185.           ret += x(cur);
  1186.           continue;
  1187.         }
  1188.         if (cur - last == 1) {
  1189.           last = cur;
  1190.           continue;
  1191.         }
  1192.   
  1193.         if(last != first) ret += "-" + x(last);
  1194.         ret+= x(cur);
  1195.         last = first = cur;
  1196.       }
  1197.     }
  1198.     return ret;
  1199.   },
  1200.   get invalidChars() {
  1201.     delete this.invalidChars;
  1202.     return this.invalidChars = new RegExp("^[^\"'/]*[" + this._createInvalidRanges() + "][^\"'/]*$");
  1203.   },
  1204.   checkJSBreak: function(s) {
  1205.     // Direct script injection breaking JS string literals or comments
  1206.     
  1207.     // cleanup most urlencoded noise and reduce JSON/XML
  1208.     s = this.reduceXML(this.reduceJSON(this.collapseChars(
  1209.         s.replace(/\%\d+[a-z\(]\w*/gi, '`')
  1210.           .replace(/[\r\n]+/g, "\n")
  1211.           .replace(/[\x01-\x09\x0b-\x20]+/g, ' ')
  1212.         )));
  1213.     
  1214.     if (!this.maybeJS(s)) return false;
  1215.     
  1216.     const invalidChars = this.invalidChars;
  1217.     const findInjection = 
  1218.       /(['"#;]|[\/\?=&](?![\?=&])|\*\/)(?=([\s\S]*?(?:\(|\[[\s\S]*?\]|(?:s\W*e\W*t\W*t\W*e\W*r|l\W*o\W*c\W*a\W*t\W*i\W*o\W*n|i\W*n\W*n\W*e\W*r\W*H\W*T\W*M\W*L|\W*o\W*n(?:\W*\w){3,}|\.\D)[^&]*=[\s\S]*?[\w\$\u0080-\uFFFF\.\[\]\-]+)))/g;
  1219.     
  1220.     findInjection.lastIndex = 0;
  1221.     var m, breakSeq, subj, expr, lastExpr, script,
  1222.       quote, len, bs, bsPos, hunt, moved, errmsg, pos;
  1223.     
  1224.     const MAX_TIME = 8000, MAX_LOOPS = 600;
  1225.  
  1226.     const t = Date.now();
  1227.     var iterations = 0;
  1228.     
  1229.     while ((m = findInjection.exec(s))) {
  1230.       
  1231.       subj = s.substring(findInjection.lastIndex);
  1232.       
  1233.       // remove common C++ style comment pattern, if possible
  1234.       
  1235.       if (!this.maybeJS(subj)) {
  1236.          this.log("Fast escape on " + subj, t, iterations);
  1237.          return false;
  1238.       }
  1239.       
  1240.       
  1241.       breakSeq = m[1];
  1242.       
  1243.       script = this.reduceURLs(subj);
  1244.       if (script.length < subj.length) {
  1245.         if (!this.maybeJS(script)) {
  1246.           this.log("Skipping to first nested URL in " + subj, t, iterations);
  1247.           findInjection.lastIndex += subj.indexOf("://") + 1;
  1248.           continue;
  1249.         }
  1250.         subj = script;
  1251.         script = this.reduceURLs(m[2]);
  1252.       } else {
  1253.         script = m[2];
  1254.       }
  1255.       
  1256.       expr = subj.match(/^[\s\S]*?[=\)]/);
  1257.       if (expr) {
  1258.         expr = expr[0];
  1259.         if (expr.length < script.length) {
  1260.           expr = script;
  1261.         }
  1262.       } else {
  1263.         expr = script;
  1264.       }
  1265.       
  1266.       // quickly skip (mis)leading innocuous CGI patterns
  1267.       if ((m = subj.match(
  1268.         /^(?:(?:[\.\?\w\-\/&:`\[\]]+=[\w \-:\+%#,`\.]*(?:[&\|](?=[^&\|])|$)){2,}|\w+:\/\/\w[\w\-\.]*)/
  1269.         // r2l, chained query string parameters, protocol://domain, ...
  1270.         ))) {
  1271.        
  1272.         this.log("Skipping CGI pattern in " + subj);
  1273.         findInjection.lastIndex += m[0].length - 1;
  1274.         continue;
  1275.       }
  1276.       
  1277.      
  1278.       
  1279.       quote = breakSeq == '"' || breakSeq == "'" ? breakSeq : '';
  1280.       bs = this.breakStops[quote || 'nq']  
  1281.  
  1282.       len = expr.length;
  1283.       
  1284.       for (moved = false, hunt = !!expr, lastExpr; hunt;) {
  1285.         
  1286.         if (Date.now() - t > MAX_TIME) {
  1287.           this.log("Too long execution time! Assuming DOS... " + s, t, iterations);
  1288.           return true;
  1289.         }
  1290.         
  1291.         hunt = expr.length < subj.length;
  1292.         
  1293.         if (moved) {
  1294.           moved = false;
  1295.         } else if (hunt) {
  1296.           bsPos = subj.substring(len).search(bs);
  1297.           if (bsPos < 0) {
  1298.             expr = subj;
  1299.             hunt = false;
  1300.           } else {
  1301.             len += bsPos;
  1302.             if (quote && subj[len] == quote) {
  1303.               len++;
  1304.             }
  1305.             expr = subj.substring(0, len);
  1306.             if (bsPos == 0) len++;
  1307.           }
  1308.         }
  1309.         
  1310.         if(lastExpr === expr) {
  1311.           lastExpr = null;
  1312.           continue;
  1313.         }
  1314.         lastExpr = expr;
  1315.         
  1316.         if(invalidChars.test(expr)) {
  1317.           this.log("Quick skipping invalid chars");
  1318.           continue;
  1319.         }
  1320.         
  1321.         if(quote) {
  1322.           script = this.syntax.unquote(quote + expr, quote);
  1323.           if(script && this.maybeJS(script) &&
  1324.             (this.checkNonTrivialJSSyntax(script) ||
  1325.               /'.+/.test(script) && this.checkNonTrivialJSSyntax("''" + script + "'") ||
  1326.               /".+/.test(script) && this.checkNonTrivialJSSyntax('""' + script + '"')
  1327.             ) && this.checkLastFunction()
  1328.             ) {
  1329.             this.log("JS quote Break Injection detected", t, iterations);
  1330.             return true;
  1331.           }
  1332.           script = quote + quote + expr + quote;
  1333.         } else {
  1334.           script = expr;
  1335.         }
  1336.         
  1337.         
  1338.         if (/^(?:[^'"\/\[\(]*[\]\)]|[^"'\/]*(?:`|[^&]&[\w\.]+=[^=]))/
  1339.             .test(script.split("//")[0])) {
  1340.            this.log("SKIP (head syntax) " + script, t, iterations);
  1341.            break; // unrepairable syntax error in the head move left cursor forward 
  1342.         }
  1343.         
  1344.         if (this.maybeJS(this.reduceQuotes(script))) {
  1345.  
  1346.           if (this.checkJSSyntax(script) && this.checkLastFunction()) {
  1347.             this.log("JS Break Injection detected", t, iterations);
  1348.             return true;
  1349.           }
  1350.           if (++iterations > MAX_LOOPS) {
  1351.             this.log("Too many syntax checks! Assuming DOS... " + s, t, iterations);
  1352.             return true;
  1353.           }
  1354.           if(this.syntax.lastError) { // could be null if we're here thanks to checkLastFunction()
  1355.             errmsg = this.syntax.lastError.message;
  1356.             this.log(errmsg + " --- " + script + " --- ", t, iterations);
  1357.             if(!quote) {
  1358.               if (/left-hand/.test(errmsg)) {
  1359.                 m = subj.match(/^([^\]\(\\'"=\?]+?)[\w$\u0080-\uffff\s]+[=\?]/);
  1360.                 if (m) {
  1361.                   findInjection.lastIndex += m[1].length - 1;
  1362.                 }
  1363.                 break;
  1364.               } else if (/unterminated string literal/.test(errmsg)) {
  1365.                 bsPos = subj.substring(len).search(/["']/);
  1366.                 if(bsPos > -1) {
  1367.                   expr = subj.substring(0, len += bsPos + 1);
  1368.                   moved = true;
  1369.                 } else break;
  1370.               } else if (/syntax error/.test(errmsg)) {
  1371.                 bsPos = subj.indexOf("//");
  1372.                 if (bsPos > -1) {
  1373.                   pos = subj.search(/['"\n\\\(]|\/\*/);
  1374.                   if (pos < 0 || pos > bsPos)
  1375.                     break;
  1376.                 }
  1377.                 if (/^([\w\[\]]*=)?\w*&[\w\[\]]*=/.test(subj)) { // CGI param concatenation
  1378.                   break;
  1379.                 }
  1380.               }
  1381.             } else if (/left-hand/.test(errmsg)) break;
  1382.             
  1383.             if (/invalid .*\bflag\b|missing ; before statement|invalid label|illegal character|identifier starts immediately/.test(errmsg)) {
  1384.               if (!(/illegal character/.test(errmsg) && /#\d*\s*$/.test(script))) // sharp vars exceptional behavior
  1385.                 break; // unrepairable syntax error, move left cursor forward 
  1386.             }
  1387.             else if((m = errmsg.match(/\bmissing ([:\]\)\}]) /))) {
  1388.               len = subj.indexOf(m[1], len);
  1389.               if (len > -1) {
  1390.                 expr = subj.substring(0, ++len);
  1391.                 moved = m[1] != ':';
  1392.               } else break;
  1393.             }
  1394.           }
  1395.         }
  1396.       }
  1397.     }
  1398.     this.log(s, t, iterations);
  1399.     return false;
  1400.   },
  1401.   
  1402.   
  1403.   checkJS: function(s, opts) {
  1404.     this.log(s);
  1405.     // recursive escaping options
  1406.     if (!opts) opts = { uni: true, ent: true };
  1407.     
  1408.     var hasUnicodeEscapes = opts.uni && /\\u[0-9a-f]{4}/.test(s);
  1409.     if (hasUnicodeEscapes && /\\u00(?:22|27|2f)/i.test(s)) {
  1410.       this.log("Unicode-escaped lower ASCII, why would you?");
  1411.       return true;
  1412.     }
  1413.     
  1414.     // the hardcore job!
  1415.     if (this.checkAttributes(s)) return true;
  1416.     if (/[\\\(]|=[^=]/.test(s) && // quick preliminary screen
  1417.         this.checkJSBreak(s))
  1418.       return true;
  1419.     
  1420.     
  1421.     // recursive cross-unescaping
  1422.     
  1423.     if (hasUnicodeEscapes &&
  1424.         this.checkJS(this.unescapeJS(s), {
  1425.           ent: false, // even if we introduce new entities, they're unrelevant because happen post-spidermonkey
  1426.           uni: false
  1427.         })) 
  1428.       return true;
  1429.     
  1430.     if (opts.ent) {
  1431.       var converted = Entities.convertAll(s);
  1432.       if (converted != s && this.checkJS(converted, {
  1433.           ent: false,
  1434.           uni: true // we might have introduced new unicode escapes
  1435.         }))
  1436.         return true;
  1437.     }
  1438.     
  1439.     return false;
  1440.   },
  1441.   
  1442.   unescapeJS: function(s) {
  1443.     return s.replace(/\\u([0-9a-f]{4})/gi, function(s, c) {
  1444.       return String.fromCharCode(parseInt(c, 16));
  1445.     });
  1446.   },
  1447.   
  1448.   unescapeCSS: function(s) {
  1449.     // see http://www.w3.org/TR/CSS21/syndata.html#characters
  1450.     return s.replace(/\\([\da-f]{0,6})\s?/gi, function($0, $1) {
  1451.       try {
  1452.         return String.fromCharCode(parseInt($1, 16));
  1453.       } catch(e) {
  1454.         return "";
  1455.       }
  1456.     });
  1457.   },
  1458.   
  1459.   reduceDashPlus: function(s) {
  1460.     // http://forums.mozillazine.org/viewtopic.php?p=5592865#p5592865
  1461.     return s.replace(/\-+/g, "-")
  1462.         .replace(/\++/g, "+")
  1463.         .replace(/\s+/g, ' ')
  1464.         .replace(/(?: \-)+/g, ' -')
  1465.         .replace(/(?:\+\-)+/g, '+-'); 
  1466.   },
  1467.   
  1468.   attributesChecker: new RegExp(
  1469.       "\\W(?:javascript:[\\s\\S]+(?:[=\\(]|%(?:[3a]8|[3b]d))|data:[\\w\\-/]+[;,])|@" + 
  1470.       ("import\\W*(?:\\/\\*[\\s\\S]*)?(?:[\"']|url[\\s\\S]*\\()" + 
  1471.         "|-moz-binding[\\s\\S]*:[\\s\\S]*url[\\s\\S]*\\(")
  1472.         .replace(/[a-rt-z\-]/g, "\\W*$&"), 
  1473.       "i"),
  1474.   checkAttributes: function(s) {
  1475.     s = this.reduceDashPlus(s);
  1476.     return this.attributesChecker.test(s) ||
  1477.         /\\/.test(s) && this.attributesChecker.test(this.unescapeCSS(s));
  1478.   },
  1479.   
  1480.   HTMLChecker: new RegExp("<[^\\w<>]*(?:[^<>\"'\\s]*:)?[^\\w<>]*(?:" + // take in account quirks and namespaces
  1481.    fuzzify("script|form|style|link|object|embed|applet|param|iframe|frame|base|body|meta|ima?g|svg|video|audio|marquee") + 
  1482.     ")|(?:<[^>]+|'[^>']*|\"[^>\"]*|\\s+)\\b" + IC_EVENT_PATTERN + "[\\s\\x08]*=", 
  1483.     "i"),
  1484.   checkHTML: function(s) {
  1485.     this.log(s);
  1486.     return this.HTMLChecker.test(s);
  1487.   },
  1488.   
  1489.   NoscriptChecker: new RegExp("<[^\\w<>]*(?:[^<>\"'\\s]*:)?[^\\w<>]*(?:" +
  1490.     fuzzify("form|style|link|object|embed|applet|iframe|frame|meta|svg|video|audio") + ")"
  1491.     ),
  1492.   checkNoscript: function(s) {
  1493.     this.log(s);
  1494.     return this.NoscriptChecker.test(s);
  1495.   },
  1496.   
  1497.   base64: false,
  1498.   base64tested: [],
  1499.   get base64Decoder() { return Base64 }, // exposed here just for debugging purposes
  1500.   checkBase64: function(url) {
  1501.     this.log(url);
  1502.     var t = Date.now();
  1503.     var frags, curf, j, k, pos, ff, f;
  1504.     const MAX_TIME = 4000;
  1505.     const DOS_MSG = "Too long execution time, assuming DOS in Base64 checks";
  1506.     this.base64 = false;
  1507.     // standard base64
  1508.     // notice that we cut at 8192 chars because of stack overflow in JS regexp implementation
  1509.     // (limit appears to be 65335, but cutting here seems quicker for big strings)
  1510.     // therefore we need to rejoin continuous strings manually
  1511.     url = url.replace(/\s+/g, ''); // base64 can be splitted across lines
  1512.     frags = url.match(/[A-Za-z0-9\+\/]{12,8191}=*[^A-Za-z0-9\+\/=]?/g);
  1513.     if (frags) {
  1514.       f = '';
  1515.       for (j = 0; j < frags.length; j++) {
  1516.         curf = frags[j];
  1517.         if (/[A-Za-z0-9\+\/]$/.test(curf)) {
  1518.           f += curf;
  1519.           if (j < frags.length - 1) continue;
  1520.         } else {
  1521.           f += curf.substring(0, curf.length - 1);
  1522.         }
  1523.         ff = f.split('/');
  1524.         if (ff.length > 255) {
  1525.           this.log("More than 255 base64 slash chunks, assuming DOS");
  1526.           return true;
  1527.         }
  1528.         while (ff.length) {
  1529.           
  1530.           if (Date.now() - t > MAX_TIME) {
  1531.               this.log(DOS_MSG);
  1532.               return true;
  1533.           }
  1534.           f = ff.join('/');
  1535.           if (f.length >= 12 && this.checkBase64Frag(f))
  1536.             return true;
  1537.           
  1538.           ff.shift();
  1539.         }
  1540.         f = '';
  1541.       }
  1542.     }
  1543.     // URL base64 variant, see http://en.wikipedia.org/wiki/Base64#URL_applications
  1544.     frags = url.match(/[A-Za-z0-9\-_]{12,8191}[^A-Za-z0-9\-_]?/g);
  1545.     if (frags) {
  1546.       f = '';
  1547.       for (j = 0; j < frags.length; j++) {
  1548.         if (Date.now() - t > MAX_TIME) {
  1549.           this.log(DOS_MSG);
  1550.           return true;
  1551.         }
  1552.         curf = frags[j];
  1553.         if (/[A-Za-z0-9\-_]$/.test(curf)) {
  1554.           f += curf;
  1555.           if (j < frags.length - 1) continue;
  1556.         } else {
  1557.           f += curf.substring(0, curf.length - 1);
  1558.         }
  1559.         f = f.replace(/-/g, '+').replace(/_/, '/');
  1560.         if (this.checkBase64Frag(f)) return true;
  1561.         f = '';
  1562.       }
  1563.     }
  1564.     return false;
  1565.   },
  1566.   
  1567.   checkBase64Frag: function(f) {
  1568.     if (this.base64tested.indexOf(f) < 0) {
  1569.       this.base64tested.push(f);
  1570.       try {
  1571.           var s = Base64.decode(f);
  1572.           if(s && s.replace(/[^\w\(\)]/g, '').length > 7 && (this.checkHTML(s) || this.checkJS(s))) {
  1573.             this.log("Detected BASE64 encoded injection: " + f);
  1574.             return this.base64 = true;
  1575.           }
  1576.       } catch(e) {}
  1577.     }
  1578.     return false;
  1579.   },
  1580.   
  1581.   checkURL: function(url) {
  1582.     // let's assume protocol and host are safe, but keep the leading double slash to keep comments in account
  1583.     url = url.replace(/^[a-z]+:\/\/.*?(?=\/|$)/, "//");
  1584.     return this.checkRecursive(url);
  1585.   },
  1586.   
  1587.   checkRecursive: function(s, depth, isPost) {
  1588.     if (typeof(depth) != "number")
  1589.       depth = 3;
  1590.     this.isPost = isPost || false;
  1591.     this.base64 = false;
  1592.     this.base64tested = [];
  1593.     return this._checkRecursive(s, depth);
  1594.   },
  1595.   
  1596.   _checkRecursive: function(s, depth) {
  1597.     
  1598.     
  1599.     if (this.checkHTML(s) || this.checkJS(s))
  1600.       return true;
  1601.     
  1602.     if (--depth <= 0)
  1603.       return false;
  1604.     
  1605.     
  1606.     if (/\+/.test(s) && this._checkRecursive(this.urlUnescape(s.replace(/\+/g, ' '), depth)))
  1607.       return true;
  1608.     
  1609.     var unescaped = this.urlUnescape(s);
  1610.     
  1611.     if (this._checkOverDecoding(s, unescaped))
  1612.       return true;
  1613.     
  1614.     if (/[\n\r\t]/.test(unescaped) &&
  1615.         this._checkRecursive(unescaped.replace(/[\n\r\t]/g, ''), depth)) {
  1616.       this.log("Trash-stripped nested URL match!"); // http://mxr.mozilla.org/mozilla-central/source/netwerk/base/src/nsURLParsers.cpp#100
  1617.       return true;
  1618.     }
  1619.     
  1620.     if (!this.isPost && this.checkBase64(s.replace(/^\/{1,3}/, ''))) return true;
  1621.     
  1622.     if (unescaped != s && this._checkRecursive(unescaped, depth))
  1623.       return true;
  1624.     
  1625.     s = this.ebayUnescape(unescaped);
  1626.     if (s != unescaped && this._checkRecursive(s, depth))
  1627.       return true;
  1628.     
  1629.     return false;
  1630.   },
  1631.   
  1632.   _checkOverDecoding: function(s, unescaped) {
  1633.     if (/%[8-9a-f]/i.test(s)) {
  1634.       const rx = /[<'"]/g;
  1635.       var m1 = unescape(this.utf8OverDecode(s, false)).match(rx);
  1636.       if (m1) {
  1637.         unescaped = unescaped || this.urlUnescape(s);
  1638.         var m0 = unescaped.match(rx);
  1639.         if (!m0 || m0.length < m1.length) {
  1640.           this.log("Potential utf8_decode() exploit!");
  1641.           return true;
  1642.         }
  1643.       }
  1644.     }
  1645.     return false;
  1646.   },
  1647.   
  1648.   utf8OverDecode: function(url, strict) {
  1649.     return url.replace(strict
  1650.       ? /%(?:f0%80%80|e0%80|c0)%[8-b][0-f]/gi
  1651.       : /%(?:f[a-f0-9](?:%[0-9a-f]0){2}|e0%[4-9a-f]0|c[01])%[a-f0-9]{2}/gi,
  1652.       function(m) {
  1653.         var hex = m.replace(/%/g, '');
  1654.         if (strict) {
  1655.           for (var j = 2; j < hex.length; j += 2) {
  1656.             if ((parseInt(hex.substring(j, j + 2), 16) & 0xc0) != 0x80) return m;
  1657.           }
  1658.         }
  1659.         switch (hex.length) {
  1660.           case 8:
  1661.             hex = hex.substring(2);
  1662.           case 6:
  1663.             c = (parseInt(hex.substring(0, 2), 16) & 0x3f) << 12 |
  1664.                    (parseInt(hex.substring(2, 4), 16) & 0x3f) << 6 |
  1665.                     parseInt(hex.substring(4, 6), 16) & 0x3f;
  1666.             break;
  1667.           default:
  1668.             c = (parseInt(hex.substring(0, 2), 16) & 0x3f) << 6 |
  1669.                     parseInt(hex.substring(2, 4), 16) & 0x3f;
  1670.         }
  1671.         return encodeURIComponent(String.fromCharCode(c & 0x3f));
  1672.       }
  1673.     );
  1674.   },
  1675.   
  1676.   urlUnescape: function(url, brutal) {
  1677.     var od = this.utf8OverDecode(url, !brutal);
  1678.     try {
  1679.       return decodeURIComponent(od);
  1680.     } catch(warn) {
  1681.       if (url != od) url += " (" + od + ")";  
  1682.       this.log("Problem decoding " + url + ", maybe not an UTF-8 encoding? " + warn.message);
  1683.       return unescape(od);
  1684.     }
  1685.   },
  1686.   
  1687.   ebayUnescape: function(url) {
  1688.     return url.replace(/Q([\da-fA-F]{2})/g, function(s, c) {
  1689.       return String.fromCharCode(parseInt(c, 16));
  1690.     });
  1691.   },
  1692.   
  1693.   checkPost: function(channel) {
  1694.     if (!((channel instanceof CI.nsIUploadChannel)
  1695.           && channel.uploadStream && (channel.uploadStream instanceof CI.nsISeekableStream)))
  1696.       return false;
  1697.     
  1698.     var clen = -1;
  1699.     try {
  1700.       clen = chan.getRequestHeader("Content-length");
  1701.     } catch(e) {}
  1702.     MaxRunTime.increase(clen < 0 || clen > 300000 ? 60 : Math.ceil(20 * clen / 100000));
  1703.     
  1704.     this.log("Extracting post data...");
  1705.     return this.checkPostStream(channel.URI.spec, channel.uploadStream);
  1706.   },
  1707.   
  1708.   checkPostStream: function(url, stream) {
  1709.      var ic = this;
  1710.      return new PostChecker(url, stream).check(
  1711.       function(chunk) {
  1712.         return chunk.length > 6 && ic.checkRecursive(chunk, 2, true) && chunk;
  1713.       }
  1714.     );
  1715.   },
  1716.   
  1717.   testCheckPost: function(url, strData) {
  1718.     var stream = CC["@mozilla.org/io/string-input-stream;1"].
  1719.             createInstance(CI.nsIStringInputStream);
  1720.     stream.setData(strData, strData.length);
  1721.     return this.checkPostStream(url, stream);
  1722.   }
  1723.   
  1724. };
  1725.  
  1726. function PostChecker(url, uploadStream) {
  1727.   this.url = url;
  1728.   this.uploadStream = uploadStream;  
  1729. }
  1730.  
  1731. PostChecker.prototype = {
  1732.   boundary: null,
  1733.   isFile: false,
  1734.   postData: '',
  1735.   check: function(callback) {
  1736.     var m, chunks, data, size, available, ret;
  1737.     const BUF_SIZE = 3 * 1024 * 1024; // 3MB
  1738.     const MAX_FIELD_SIZE = BUF_SIZE;
  1739.     try {
  1740.       var us = this.uploadStream;
  1741.       us.seek(0, 0);
  1742.       const sis = CC['@mozilla.org/binaryinputstream;1'].createInstance(CI.nsIBinaryInputStream);
  1743.       sis.setInputStream(us);
  1744.       
  1745.       // reset status
  1746.       delete this.boundary;
  1747.       delete this.isFile;
  1748.       delete this.postData;
  1749.      
  1750.       if ((available = sis.available())) do {
  1751.         size = this.postData.length;
  1752.         if (size >= MAX_FIELD_SIZE) return size + " bytes or more in one non-file field, assuming memory DOS attempt!";
  1753.  
  1754.         data = sis.readBytes(Math.min(available, BUF_SIZE));
  1755.  
  1756.         if (size !== 0) {
  1757.           this.postData += data;
  1758.         } else {
  1759.            if (data.length === 0) return false;
  1760.            this.postData = data;
  1761.         }
  1762.         available = sis.available();
  1763.         chunks = this.parse(!available);
  1764.       
  1765.         for (var j = 0, len = chunks.length; j < len; j++) {
  1766.           ret = callback(chunks[j]);
  1767.           if (ret) return ret;
  1768.         }
  1769.       } while(available)
  1770.     } catch(ex) {
  1771.       dump(ex + "\n" + ex.stack + "\n");
  1772.       return ex;
  1773.     } finally {
  1774.         try {
  1775.           us.seek(0, 0); // rewind
  1776.         } catch(e) {}
  1777.     }
  1778.     return false; 
  1779.   },
  1780.   
  1781.   parse: function(eof) {
  1782.     var postData = this.postData;
  1783.     var m;
  1784.     
  1785.     if (typeof(this.boundary) != "string") {
  1786.       m = postData.match(/^Content-type: multipart\/form-data;\s*boundary=(\S*)/i);
  1787.       this.boundary = m && m[1] || '';
  1788.       if (this.boundary) this.boundary = "--" + this.boundary;
  1789.       postData = postData.substring(postData.indexOf("\r\n\r\n") + 2);
  1790.     }
  1791.  
  1792.     this.postData = '';
  1793.  
  1794.     var boundary = this.boundary;
  1795.    
  1796.     var chunks = [];
  1797.     var j, len;
  1798.  
  1799.     if (boundary) { // multipart/form-data, see http://www.faqs.org/ftp/rfc/rfc2388.txt  
  1800.       if(postData.indexOf(boundary) < 0) {
  1801.         // skip big file chunks
  1802.         return chunks;
  1803.       }
  1804.       var parts = postData.split(boundary);
  1805.       
  1806.       var part, last;
  1807.       for(j = 0, len = parts.length; j < len;) {
  1808.         part = parts[j];
  1809.         last = ++j == len;
  1810.         if (j == 1 && part.length && this.isFile) {
  1811.           // skip file internal terminal chunk
  1812.           this.isFile = false;
  1813.           continue;
  1814.         }
  1815.         m = part.match(/^\s*Content-Disposition: form-data; name="(.*?)"(?:;\s*filename="(.*)"|[^;])\r?\n(Content-Type: \w)?.*\r?\n/i);
  1816.         
  1817.         if (m) {
  1818.           // name and filename are backslash-quoted according to RFC822
  1819.           if (m[1]) chunks.push(m[1].replace(/\\\\/g, "\\")); // name and file name 
  1820.           if (m[2]) {
  1821.             chunks.push(m[2].replace(/\\\\/g, "\\")); // filename
  1822.             if (m[3]) {
  1823.               // Content-type: skip, it's a file
  1824.               this.isFile = true;
  1825.               
  1826.               if (last && !eof) 
  1827.                 this.postData = part.substring(part.length - boundary.length);
  1828.  
  1829.               continue; 
  1830.             }
  1831.           }
  1832.           if (eof || !last) {
  1833.             chunks.push(part.substring(m[0].length)); // parameter body
  1834.           } else {
  1835.             this.postData = part;
  1836.           }
  1837.           this.isFile = false;
  1838.         } else {
  1839.           // malformed part, check it all or push it back
  1840.           if (eof || !last) {
  1841.             chunks.push(part)
  1842.           } else {
  1843.             this.postData = this.isFile ? part.substring(part.length - boundary.length) : part;
  1844.           }
  1845.         }
  1846.       }
  1847.     } else {
  1848.       this.isFile = false;
  1849.       
  1850.       parts = postData.split("&");
  1851.       if (!eof) this.postData = parts.pop();
  1852.       
  1853.       for (j = 0, len = parts.length; j < len; j++) {
  1854.         m = parts[j].split("=");
  1855.         chunks.push(m[0]);
  1856.         if (m.length > 1) chunks.push(m[1]);
  1857.       }
  1858.     }
  1859.     return chunks;
  1860.   }
  1861. }
  1862.  
  1863.  
  1864. function XSanitizer(primaryBlacklist, extraBlacklist) {
  1865.   this.primaryBlacklist = primaryBlacklist;
  1866.   this.extraBlacklist = extraBlacklist;
  1867.   this.injectionChecker = InjectionChecker;
  1868. }
  1869.  
  1870. XSanitizer.prototype = {
  1871.   brutal: false,
  1872.   base64: false,
  1873.   sanitizeURL: function(url) {
  1874.     var original = url.clone();
  1875.     this.brutal = this.brutal || this.injectionChecker.checkURL(url.spec);
  1876.     this.base64 = this.injectionChecker.base64;
  1877.     
  1878.     const changes = { minor: false, major: false, qs: false };
  1879.     // sanitize credentials
  1880.     if (url.username) url.username = this.sanitizeEnc(url.username);
  1881.     if (url.password) url.password = this.sanitizeEnc(url.password);
  1882.     url.host = this.sanitizeEnc(url.host);
  1883.     
  1884.     if (url instanceof CI.nsIURL) {
  1885.       // sanitize path
  1886.      
  1887.       if (url.param) {
  1888.         url.path = this.sanitizeURIComponent(url.path); // param is the URL part after filePath and a semicolon ?!
  1889.       } else if(url.filePath) { 
  1890.         url.filePath = this.sanitizeURIComponent(url.filePath); // true == lenient == allow ()=
  1891.       }1
  1892.       // sanitize query
  1893.       if (url.query) {
  1894.         url.query = this.sanitizeQuery(url.query, changes);
  1895.         if (this.brutal) {
  1896.           url.query = this.sanitizeWholeQuery(url.query, changes);
  1897.         }
  1898.       }
  1899.       // sanitize fragment
  1900.       var fragPos = url.path.indexOf("#");
  1901.       if (url.ref || fragPos > -1) {
  1902.         if (fragPos >= url.filePath.length + url.query.length) {
  1903.           url.path = url.path.substring(0, fragPos) + "#" + this.sanitizeEnc(url.path.substring(fragPos + 1));
  1904.         } else {
  1905.           url.ref = this.sanitizeEnc(url.ref);
  1906.         }
  1907.       }
  1908.     } else {
  1909.       // fallback for non-URL URIs, we should never get here anyway
  1910.       if (url.path) url.path = this.sanitizeURIComponent(url.path);
  1911.     }
  1912.     
  1913.     var urlSpec = url.spec;
  1914.     var neutralized = Entities.neutralizeAll(urlSpec, /[^\\'"\x00-\x07\x09\x0B\x0C\x0E-\x1F\x7F<>]/);
  1915.     if (urlSpec != neutralized) url.spec = neutralized;
  1916.     
  1917.     if (this.base64) {
  1918.       url.spec = url.prePath; // drastic, but with base64 we cannot take the risk!
  1919.     }
  1920.     
  1921.     if (url.getRelativeSpec(original) && unescape(url.spec) != unescape(original.spec)) { // ok, this seems overkill but take my word, the double check is needed
  1922.       changes.minor = true;
  1923.       changes.major = changes.major || changes.qs || 
  1924.                       unescape(original.spec.replace(/\?.*/g, "")) 
  1925.                         != unescape(url.spec.replace(/\?.*/g, ""));
  1926.       url.spec = url.spec.replace(/'/g, "%27")
  1927.       if (changes.major) {
  1928.         url.ref = Math.random().toString().concat(Math.round(Math.random() * 999 + 1)).replace(/0./, '') // randomize URI
  1929.       }
  1930.     } else {
  1931.       changes.minor = false;
  1932.       url.spec = original.spec.replace(/'/g, "%27");
  1933.     }
  1934.     return changes;
  1935.   },
  1936.   
  1937.   sanitizeWholeQuery: function(query, changes) {
  1938.     var original = query;
  1939.     query = Entities.convertAll(query);
  1940.     if (query == original) return query;
  1941.     var unescaped = InjectionChecker.urlUnescape(original, true);
  1942.     query = this.sanitize(unescaped);
  1943.     if (query == unescaped) return original;
  1944.     if(changes) changes.qs = true;
  1945.     return escape(query);
  1946.   },
  1947.   
  1948.   _queryRecursionLevel: 0,
  1949.   sanitizeQuery: function(query, changes, sep) {
  1950.     const MAX_RECUR = 2;
  1951.     
  1952.     var canRecur = this._queryRecursionLevel++ < MAX_RECUR;
  1953.     // replace every character matching noscript.filterXGetRx with a single ASCII space (0x20)
  1954.     changes = changes || {};
  1955.     if (!sep) {
  1956.       sep = query.indexOf("&") > -1 ? "&" : ";" 
  1957.     }
  1958.     const parms = query.split(sep);
  1959.     var j, pieces, k, pz, origPz, encodedPz, nestedURI, qpos, apos, encodeURL;
  1960.     
  1961.     for (j = parms.length; j-- > 0;) {
  1962.       pieces = parms[j].split("=");
  1963.       
  1964.       
  1965.       try {
  1966.         for (k = pieces.length; k-- > 0;) {
  1967.           encodedPz =  InjectionChecker.utf8OverDecode(pieces[k]);
  1968.           pz = null;
  1969.           if (encodedPz.indexOf("+") < 0) {
  1970.             try {
  1971.               pz = decodeURIComponent(encodedPz);
  1972.               encodeURL = encodeURIComponent;
  1973.             } catch(e) {}
  1974.           }
  1975.           if (pz == null) {
  1976.             pz = unescape(encodedPz);
  1977.             encodeURL = escape;
  1978.           }
  1979.           origPz = pz;
  1980.           
  1981.           // recursion for nested (partial?) URIs
  1982.  
  1983.           nestedURI = null;
  1984.           
  1985.           if (canRecur && /^https?:\/\//i.test(pz)) {
  1986.             // try to sanitize as a nested URL
  1987.             try {
  1988.               nestedURI = IOS.newURI(pz, null, null).QueryInterface(CI.nsIURL);
  1989.               changes.qs = changes.qs || this.sanitizeURL(nestedURI).major;
  1990.               if (unescape(pz).replace(/\/+$/, '') != unescape(nestedURI.spec).replace(/\/+$/, '')) pz = nestedURI.spec;
  1991.             } catch(e) {
  1992.               nestedURI = null;
  1993.             }
  1994.           }
  1995.           
  1996.           if (!nestedURI) {
  1997.             if (canRecur &&
  1998.                  (qpos = pz.indexOf("?")) > - 1 &&
  1999.                  (spos = pz.search(/[&;]/) > qpos)) { 
  2000.               // recursive query string?
  2001.               // split, sanitize and rejoin
  2002.               pz = [ this.sanitize(pz.substring(0, qpos)), 
  2003.                     this.sanitizeQuery(pz.substring(qpos + 1), changes)
  2004.                    ].join("?")
  2005.               
  2006.             } else {
  2007.               pz = this.sanitize(pz);
  2008.             }
  2009.             if (origPz != pz) changes.qs = true;
  2010.           }
  2011.           
  2012.           if (origPz != pz) pieces[k] = encodeURL(pz);  
  2013.          
  2014.         }
  2015.         parms[j] = pieces.join("=");
  2016.       } catch(e) { 
  2017.         // decoding exception, skip this param
  2018.         parms.splice(j, 1);
  2019.       } 
  2020.     }
  2021.     this._queryRecursionLevel--;
  2022.     return parms.join(sep);
  2023.   },
  2024.   
  2025.   sanitizeURIComponent: function(s) {
  2026.     try {
  2027.       var unescaped = InjectionChecker.urlUnescape(s, this.brutal);
  2028.       var sanitized = this.sanitize(unescaped);
  2029.       return sanitized == unescaped ? s : encodeURI(sanitized);
  2030.     } catch(e) {
  2031.       return "";
  2032.     }
  2033.   },
  2034.   sanitizeEnc: function(s) {
  2035.     try {
  2036.       return encodeURIComponent(this.sanitize(decodeURIComponent(s)));
  2037.     } catch(e) {
  2038.       return "";
  2039.     }
  2040.   },
  2041.   sanitize: function(unsanitized) {
  2042.     // deeply convert entities
  2043.     var s, orig;
  2044.     orig = s = Entities.convertDeep(unsanitized);
  2045.     
  2046.     if (s.indexOf('"') > -1 && !this.brutal) {
  2047.       // try to play nice on search engine queries with grouped quoted elements
  2048.       // by allowing double quotes but stripping even more aggressively other chars
  2049.       
  2050.       // Google preserves "$" and recognizes ~, + and ".." as operators
  2051.       // All the other non alphanumeric chars (aside double quotes) are ignored.
  2052.       // We will preserve the site: modifier as well
  2053.       // Ref.: http://www.google.com/help/refinesearch.html
  2054.       s = s.replace(/[^\w\$\+\.\~"&;\- :\u0080-\uffff]/g, 
  2055.           " " // strip everything but alphnum and operators
  2056.           ).replace(":", 
  2057.           function(k, pos, s) { // strip colons as well, unless it's the site: operator
  2058.             return (s.substring(0, pos) == "site" || s.substring(pos - 5) == " site") ? ":" : " " 
  2059.           }
  2060.         );
  2061.       if (s.replace(/[^"]/g, "").length % 2) s += '"'; // close unpaired quotes
  2062.       return s;
  2063.     }
  2064.     // regular duty
  2065.     s = s.replace(this.primaryBlacklist, " ");
  2066.     
  2067.     s = s.replace(/\bjavascript:+|\bdata:+[\s\w\-\/]*,|-moz-binding|@import/ig, function(m) { return m.replace(/\W/g, " "); });
  2068.     
  2069.     if (this.extraBlacklist) { // additional user-defined blacklist for emergencies
  2070.       s = s.replace(this.extraBlacklist, " "); 
  2071.     }
  2072.     
  2073.     if (this.brutal) { // injection checks were positive
  2074.       s = InjectionChecker.reduceDashPlus(s)
  2075.         .replace(/['\(\)\=\[\]]/g, " ")
  2076.         .replace(this._brutalReplRx, String.toUpperCase)
  2077.         .replace(/Q[\da-fA-Fa]{2}/g, "Q20")
  2078.         .replace(/%[\n\r\t]*[0-9a-f][\n\r\t]*[0-9a-f]/gi, " ")
  2079.         ; // Ebay-style escaping
  2080.     }
  2081.     
  2082.     return s == orig ? unsanitized : s;
  2083.   },
  2084.   
  2085.   _regularReplRx: new RegExp(
  2086.     fuzzify('(?:javascript|data)') + '\\W*:+|' +
  2087.       fuzzify('-moz-binding|@import'), 
  2088.     "ig"
  2089.   ),
  2090.   _brutalReplRx: new RegExp(
  2091.     '(?:' + fuzzify('setter|location|innerHTML|cookie|name|document|toString|') +
  2092.     IC_WINDOW_OPENER_PATTERN + '|' + IC_EVENT_PATTERN + ')',
  2093.     "g"
  2094.   )
  2095.   
  2096. };
  2097.  
  2098. // we need this because of https://bugzilla.mozilla.org/show_bug.cgi?id=439276
  2099.  
  2100. const Base64 = {
  2101.  
  2102.   decode : function (input) {
  2103.     var output = '';
  2104.     var chr1, chr2, chr3;
  2105.     var enc1, enc2, enc3, enc4;
  2106.     var i = 0;
  2107.     
  2108.     // if (/[^A-Za-z0-9\+\/\=]/.test(input)) return ""; // we don't need this, caller checks for us
  2109.  
  2110.     const k = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  2111.     while (i < input.length) {
  2112.  
  2113.         enc1 = k.indexOf(input.charAt(i++));
  2114.         enc2 = k.indexOf(input.charAt(i++));
  2115.         enc3 = k.indexOf(input.charAt(i++));
  2116.         enc4 = k.indexOf(input.charAt(i++));
  2117.  
  2118.         chr1 = (enc1 << 2) | (enc2 >> 4);
  2119.         chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  2120.         chr3 = ((enc3 & 3) << 6) | enc4;
  2121.  
  2122.         output += String.fromCharCode(chr1);
  2123.  
  2124.         if (enc3 != 64) {
  2125.           output += String.fromCharCode(chr2);
  2126.         }
  2127.         if (enc4 != 64) {
  2128.           output += String.fromCharCode(chr3);
  2129.         }
  2130.  
  2131.     }
  2132.     return output;
  2133.  
  2134.   }
  2135. };
  2136.  
  2137.  
  2138. function RequestInfo(channel, url, origin, window) {
  2139.   this.channel = channel;
  2140.   this.sanitizedURI = url;
  2141.   this.window = window;
  2142.   this.unsafeRequest = {
  2143.     URI: url.clone(),
  2144.     postData: null,
  2145.     referrer: channel.referrer && channel.referrer.clone(),
  2146.     origin: origin,
  2147.     loadFlags: channel.loadFlags,
  2148.     issued: false,
  2149.     window: null
  2150.   }
  2151. }
  2152. RequestInfo.prototype = {
  2153.   xssMaybe: false 
  2154. }
  2155.  
  2156.  
  2157. function DOSChecker(request, canSpin) {
  2158.   this.request = request;
  2159.   this.canSpin = canSpin;
  2160.   Thread.asap(this.check, this);
  2161. }
  2162.  
  2163. DOSChecker.abort = function(req, info) {
  2164.   IOUtil.abort(("channel" in req) ? req.channel : req, true);
  2165.   ns.log("[NoScript DOS] Aborted potential DOS attempt: " +
  2166.          ( ("name" in req) ? req.name : req ) +
  2167.          "\n" + (info || new Error().stack));
  2168. };
  2169.  
  2170. DOSChecker.prototype = {
  2171.   done: false,
  2172.   lastClosure: null,
  2173.   run: function(closure, self) {
  2174.     this.done = false;
  2175.     this.lastClosure = closure;
  2176.     try {
  2177.       return  self ? closure.apply(self) : closure();
  2178.     } finally {
  2179.       this.done = true;
  2180.     }
  2181.   },
  2182.   check: function() {
  2183.     MaxRunTime.restore();
  2184.     
  2185.     if (!(this.done || this.canSpin && Thread.activeLoops))
  2186.       DOSChecker.abort(this.request, (this.lastClosure && this.lastClosure.toSource()));
  2187.   }
  2188. }
  2189.  
  2190. var MaxRunTime = {
  2191.   branch: CC["@mozilla.org/preferences-service;1"]
  2192.         .getService(CI.nsIPrefService).getBranch("dom."),
  2193.   pref: "max_script_run_time",
  2194.   increase: function(v) {
  2195.     var cur;
  2196.     try {
  2197.       cur = this.branch.getIntPref(this.pref);
  2198.     } catch(e) {
  2199.       cur = -1;
  2200.     }
  2201.     if (cur <= 0 || cur >= v) return;
  2202.     if (typeof(this.storedValue) === "undefined") try {
  2203.       this.storedValue = cur;
  2204.     } catch(e) {}
  2205.     this.branch.setIntPref(this.pref, v);
  2206.   },
  2207.   restore: function() {
  2208.     if (typeof(this.storedValue) !== "undefined") {
  2209.       this.branch.setIntPref(this.pref, this.storedValue);
  2210.       delete this.storedValue;
  2211.     }
  2212.   }
  2213. }